//
// Created by Administrator on 2022/12/21.
//

#ifndef CONCURRENCE_QUEUE_V2_H
#define CONCURRENCE_QUEUE_V2_H

#include <mutex>
#include <memory>
#include <condition_variable>

namespace edward::multithread {

    //精细粒度锁的线程安全队列
    template<typename T>
    class Queue {
        using Type = T;
        using Ptr = std::shared_ptr<T>;
    public:
        Queue() : head_(new Node), tail_(head_.get()) {}
        Queue(const Queue&) = delete;
        Queue& operator= (const Queue&) = delete;
        void push(T t) {
            Ptr data = std::make_shared<T>(std::move(t));   //将内存分配等操作放在临界区外
            NodePtr dummyNode {new Node};
            Node* const newTail = dummyNode.get();
            {
                std::lock_guard lk(mtxTail_);   //C++17支持模板类的模板参数推断
                tail_->data_ = data;
                tail_->next_ = std::move(dummyNode);
                tail_ = newTail;
            }
            cond_.notify_one();
        }

        Ptr tryPop() {
            NodePtr tmpHead;   //在临界区外释放头节点
            {
                std::lock_guard lk(mtxHead_);
                if (head_.get() == getTail()) {
                    return Ptr{};
                }

                //head_ = head_->next_; 但是unique_ptr不能直接赋值，必须用一个临时节点接管head_，然后再赋值
                tmpHead = std::move(head_);     //防止由于给head赋值释放掉head->next
                head_ = std::move(tmpHead->next_);
            }
            return tmpHead->data_; //将函数返回放在临界区外
        }

        Ptr pop() {
            NodePtr tmpHead;
            {
                std::unique_lock lk(mtxHead_);
                cond_.wait(lk, [this]() {
                    return head_.get() != getTail();
                });

                tmpHead = std::move(head_);
                head_ = std::move(tmpHead->next_);
            }
            return tmpHead->data_;
        }

        bool empty() const {
            std::lock_guard lk(mtxHead_);
            return head_.get() == getTail();
        }

    private:
        struct Node {
            Ptr data_;
            std::unique_ptr<Node> next_;
        };
        using NodePtr = std::unique_ptr<Node>;
        NodePtr head_;
        Node* tail_;
        mutable std::mutex mtxHead_, mtxTail_;
        mutable std::condition_variable cond_;

        Node* getTail() const {
            std::lock_guard lk(mtxTail_);
            return tail_;
        }
    };

}

#endif //CONCURRENCE_QUEUE_V2_H